<html>
    <head>
        <title>自定义或覆盖服务</title>
    </head>
    <body>
        <script>
            // 自定义或覆盖服务

                /*
                        你可能需要为你的应用程序更改依赖模块的行为（业务逻辑）。

                        在这种情况下，你可以使用依赖注入系统的强大功能，通过你自己的实现替换依赖模块的服务、控制器甚至页面模型。

                        对于注册到依赖注入的任何类型的类，包括ABP的服务，都可以替换服务

                        根据你的需求，你可以使用不同的选项，这些选项将在下一节中解释。


                        ★、注意：

                                请注意，某些服务方法可能不是虚拟的，因此你可能无法覆盖。

                                我们通过设计使所有虚拟 virtual。

                                如果你发现任何不可覆盖的方法，请创建问题或自己动手并在GitHub上发送拉取请求。
                */

                /*
                    一、替换接口的实现

                                    如果给定的服务定义了一个接口，比如IdentityUserAppService类实现了IIdentityUserAppService，

                                    可以重新实现相同的接口并用你的类替换当前的实现。
                                    
                                    示例：

                                                // 自己重新实现一个IIdentityUserAppService接口
                                                public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency
                                                {
                                                    //...
                                                }

                                    MyIdentityUserAppService通过命名约定替换IIdentityUserAppService（因为两端都有IdentityUserAppService）。

                                    如果你的类名不匹配，你需要手动公开服务接口：

                                                [ExposeServices(typeof(IIdentityUserAppService))]  // 手动暴露接口实现
                                                public class TestAppService : IIdentityUserAppService, ITransientDependency
                                                {
                                                    //...
                                                }

                                    依赖注入系统允许为同一个接口注册多个服务。

                                    注入接口时使用最后一个注册的。显式替换服务是一个很好的做法。

                                                [Dependency(ReplaceServices = true)]  // 替换服务
                                                [ExposeServices(typeof(IIdentityUserAppService))]  // 暴露到哪个接口
                                                public class TestAppService : IIdentityUserAppService, ITransientDependency
                                                {
                                                    //...
                                                }

                                    这样，系统中将只有一个IIdentityUserAppService的实现，而它不会改变这种情况下的结果。
                                    
                                    也可以通过代码替换服务：

                                                context.Services.Replace(
                                                    ServiceDescriptor.Transient<IIdentityUserAppService, MyIdentityUserAppService>()
                                                );

                                                你可以将其写入模块的ConfigureServices方法中。
                            
                */

                /*
                    二、覆盖服务类  Overriding a Service Class

                                大多数情况下，你会希望更改服务当前实现的一个或几个方法。

                                在这种情况下，重新实现完整的接口效率不高。

                                作为更好的方法，"从原始类继承并覆盖所需的方法"。

                                eg: 覆盖应用程序服务

                                                [Dependency(ReplaceServices = true)]  // 替换接口服务
                                                [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))]
                                                public class MyIdentityUserAppService : IdentityUserAppService
                                                {
                                                    //  覆盖构造函数，以便我们可以注入我们自己的依赖项。

                                                    public MyIdentityUserAppService(
                                                        IdentityUserManager userManager,
                                                        IIdentityUserRepository userRepository,
                                                        IGuidGenerator guidGenerator
                                                    ) : base(
                                                        userManager,
                                                        userRepository,
                                                        guidGenerator)
                                                    {
                                                    }


                                                    // 重写父类的方法
                                                    public async override Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
                                                    {
                                                        // 添加对电话号码的验证
                                                        if (input.PhoneNumber.IsNullOrWhiteSpace())
                                                        {
                                                            throw new AbpValidationException(
                                                                "Phone number is required for new users!",
                                                                new List<ValidationResult>
                                                                {
                                                                    new ValidationResult(
                                                                        "Phone number can not be empty!",
                                                                        new []{"PhoneNumber"}
                                                                    )
                                                                }
                                                            );        }

                                                        return await base.CreateAsync(input);  // 调用父类方法继续底层业务逻辑
                                                    }
                                                }

                                        这个类覆盖IdentityUserAppServiceapplication service的CreateAsync方法来检查电话号码。

                                        然后调用base方法继续到底层业务逻辑。这样，你可以在之前执行额外的业务逻辑，在之后执行。

                                        你可以完全重写用于用户创建的整个业务逻辑，而无需调用base方法。

                */

                /*

                    三、覆盖领域服务
                                    eg: 覆盖领域服务

                                                [Dependency(ReplaceServices = true)]
                                                [ExposeServices(typeof(IdentityUserManager))]  // 暴露的服务类型
                                                public class MyIdentityUserManager : IdentityUserManager
                                                {
                                                        public MyIdentityUserManager(
                                                            IdentityUserStore store,
                                                            IIdentityRoleRepository roleRepository,
                                                            IIdentityUserRepository userRepository,
                                                            IOptions<IdentityOptions> optionsAccessor,
                                                            IPasswordHasher<IdentityUser> passwordHasher,
                                                            IEnumerable<IUserValidator<IdentityUser>> userValidators,
                                                            IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
                                                            ILookupNormalizer keyNormalizer,
                                                            IdentityErrorDescriber errors,
                                                            IServiceProvider services,
                                                            ILogger<IdentityUserManager> logger,
                                                            ICancellationTokenProvider cancellationTokenProvider) :
                                                            base(store,
                                                                roleRepository,
                                                                userRepository,
                                                                optionsAccessor,
                                                                passwordHasher,
                                                                userValidators,
                                                                passwordValidators,
                                                                keyNormalizer,
                                                                errors,
                                                                services,
                                                                logger,
                                                                cancellationTokenProvider)
                                                        {
                                                        }

                                                    public async override Task<IdentityResult> CreateAsync(IdentityUser user)
                                                    {
                                                        if (user.PhoneNumber.IsNullOrWhiteSpace())
                                                        {
                                                            throw new AbpValidationException(
                                                                "Phone number is required for new users!",
                                                                new List<ValidationResult>
                                                                {
                                                                    new ValidationResult(
                                                                        "Phone number can not be empty!",
                                                                        new []{"PhoneNumber"}
                                                                    )
                                                                }
                                                            );
                                                        }

                                                        return await base.CreateAsync(user);
                                                    }
                                                }

                                        此示例类继承自IdentityUserManager域服务并覆盖CreateAsync方法以执行上面实现的相同电话号码检查。

                                        结果是相同的，但这次我们在域服务中实现了它，假设这是我们系统的核心域逻辑。

                                                
                                        注意：
                                                [ExposeServices(typeof(IdentityUserManager))]属性是必需的因为IdentityUserManager没有定义接口（如IIdentityUserManager）
                                                
                                                并且依赖注入系统不会按照约定为继承的类公开服务（如为实现的接口）。

                */

                /*
                    四、覆盖存储库

                                                public class MyEfCoreIdentityUserRepository : EfCoreIdentityUserRepository
                                                {
                                                    public MyEfCoreIdentityUserRepository(
                                                        IDbContextProvider<IIdentityDbContext> dbContextProvider)
                                                        : base(dbContextProvider)
                                                    {
                                                    }

                                                    // 你可在此处充写任何方法
                                                }     
                                                    
                                                
                                    在此示例中，我们覆盖了由Identity模块定义的EfCoreIdentityUserRepository类。这是用户存储库的Entity Framework Core实现。

                                    由于命名约定（MyEfCoreIdentityUserRepository以EfCoreIdentityUserRepository结尾），不需要额外的设置。

                                    你可以覆盖任何基本方法来根据你的需要对其进行自定义。

                                    但是，如果你注入IRepository<IdentityUser>或IRepository<IdentityUser, Guid>，它仍将使用默认存储库实现。

                                    要替换默认存储库实现，请在模块类的ConfigureServices方法中编写以下代码：
                                     
                                                context.Services.AddDefaultRepository(
                                                    typeof(Volo.Abp.Identity.IdentityUser),
                                                    typeof(MyEfCoreIdentityUserRepository),
                                                    replaceExisting: true
                                                );

                                    通过这种方式，如果你注入IRepository<IdentityUser>、IRepository<IdentityUser, Guid>或IIdentityUserRepository，你的实现将被使用。

                                    如果你想向存储库添加"额外的"方法并在你自己的代码中使用它，你可以定义一个接口并从你的存储库实现中公开它。你还可以扩展预构建的存储库接口。
                                    
                                    例子：

                                                public interface IMyIdentityUserRepository : IIdentityUserRepository
                                                {
                                                    public Task DeleteByEmailAddress(string email);
                                                }

                                    IMyIdentityUserRepository接口扩展了Identity模块的IIdentityUserRepository接口。然后你可以实现它，如以下示例所示：

                                                [ExposeServices(typeof(IMyIdentityUserRepository), IncludeDefaults = true)]
                                                public class MyEfCoreIdentityUserRepository
                                                    : EfCoreIdentityUserRepository, IMyIdentityUserRepository
                                                {
                                                    public MyEfCoreIdentityUserRepository(
                                                        IDbContextProvider<IIdentityDbContext> dbContextProvider)
                                                        : base(dbContextProvider)
                                                    {
                                                    }

                                                    public async Task DeleteByEmailAddress(string email)
                                                    {
                                                        var dbContext = await GetDbContextAsync();
                                                        var user = await dbContext.Users.FirstOrDefaultAsync(u => u.Email == email);
                                                        if (user != null)
                                                        {
                                                            dbContext.Users.Remove(user);
                                                        }
                                                    }
                                                }
                                                
                                                MyEfCoreIdentityUserRepository类实现了IMyIdentityUserRepository接口。

                                                ExposeServices属性是必需的，因为ABP不能通过命名约定公开IMyIdentityUserRepository（MyEfCoreIdentityUserRepository不以MyIdentityUserRepository结尾）

                                                现在，你可以将IMyIdentityUserRepository接口注入服务并调用其DeleteByEmailAddress方法。

                */
                /*
                    五、覆盖控制器

                                                using System.Threading.Tasks;
                                                using Microsoft.Extensions.Logging;
                                                using Volo.Abp.Account;
                                                using Volo.Abp.DependencyInjection;

                                                namespace MyProject.Controllers
                                                {
                                                    [Dependency(ReplaceServices = true)] // 替换默认的AccountController
                                                    [ExposeServices(typeof(AccountController))] // 暴露为AccountController
                                                    public class MyAccountController : AccountController
                                                    {
                                                        public MyAccountController(IAccountAppService accountAppService)
                                                            : base(accountAppService)
                                                        {

                                                        }

                                                        // 添加一个方法
                                                        public async override Task SendPasswordResetCodeAsync(
                                                            SendPasswordResetCodeDto input)
                                                        {
                                                            Logger.LogInformation("Your custom logic...");

                                                            await base.SendPasswordResetCodeAsync(input);
                                                        }
                                                    }
                                                }
                                                    
                                    此示例替换AccountController（在帐户模块中定义的API控制器）并覆盖SendPasswordResetCodeAsync方法。  
                                    
                                    [ExposeServices(typeof(AccountController))]在这里是必不可少的，因为它在依赖注入系统中为AccountController注册此控制器。

                                    [Dependency(ReplaceServices = true)]也建议，清除旧注册（即使ASP.NETCore DI系统选择最后注册的）。

                                    此外，MyAccountController将从ApplicationModel中删除，因为它定义了ExposeServicesAttribute。

                                    如果指定了IncludeSelf = true，即[ExposeServices(typeof(AccountController), IncludeSelf = true)]，则AccountController将被删除。
                                    
                                    这对于扩展控制器很有用。

                                    如果不想删除任一控制器，可以配置AbpAspNetCoreMvcOptions： 
                                    
                                        Configure<AbpAspNetCoreMvcOptions>(options =>
                                        {
                                            options.IgnoredControllersOnModelExclusion
                                                .AddIfNotContains(typeof(MyAccountController));
                                        });
                */

                /*
                    五、扩展数据传输对象

                            扩展实体可以如扩展实体文档中所述。

                            通过这种方式，你可以将自定义属性添加到实体，并通过如上所述覆盖相关服务来执行附加业务逻辑。

                            还可以扩展应用程序服务使用的数据传输对象（DTO）。通过这种方式，你可以从UI（或客户端）获取额外的属性并从服务返回额外的属性。


                            1、如何扩展数据传输对象 Dto
                                            实例 ：
                                                            
                                                假设你已经添加了SocialSecurityNumber，如扩展实体文档中所述，
                                                
                                                并且希望在从IdentityUserAppService的GetListAsync方法获取用户列表时包含此信息。

                                                你可以使用对象扩展系统将属性添加到IdentityUserDto。

                                                在YourProjectNameDtoExtensions类中编写此代码，该类随应用程序启动模板一起提供：

                                                        ObjectExtensionManager.Instance
                                                                            .AddOrUpdateProperty<IdentityUserDto, string>(
                                                                                "SocialSecurityNumber"
                                                                            ); 
                                                                            
                                                此代码将SocialSecurityNumber到IdentityUserDto类定义为string类型。就是这么简单。

                                                现在，如果你从REST API客户端调用/api/identity/usersHTTP API（它在内部使用IdentityUserAppService），
                                                
                                                你将在extraProperties部分中看到SocialSecurityNumber值：

                                                                {
                                                                    "totalCount": 1,
                                                                    "items": [{
                                                                        "tenantId": null,
                                                                        "userName": "admin",
                                                                        "name": "admin",
                                                                        "surname": null,
                                                                        "email": "admin@abp.io",
                                                                        "emailConfirmed": false,
                                                                        "phoneNumber": null,
                                                                        "phoneNumberConfirmed": false,
                                                                        "twoFactorEnabled": false,
                                                                        "lockoutEnabled": true,
                                                                        "lockoutEnd": null,
                                                                        "concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
                                                                        "isDeleted": false,
                                                                        "deleterId": null,
                                                                        "deletionTime": null,
                                                                        "lastModificationTime": "2020-04-09T21:25:47.0740706",
                                                                        "lastModifierId": null,
                                                                        "creationTime": "2020-04-09T21:25:46.8308744",
                                                                        "creatorId": null,
                                                                        "id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
                                                                        "extraProperties": {
                                                                            "SocialSecurityNumber": "123456789"
                                                                        }
                                                                    }]
                                                                }

                                                所有预构建的模块都在其DTO中支持额外的属性，因此你可以轻松配置。

                            2、属性定义检查器 

                                        当你为一个实体定义一个额外的属性时，由于安全性，它不会自动出现在所有相关的DTO中。
                                
                                        额外的属性可能包含敏感数据，默认情况下你可能不希望将其公开给客户端。

                                        因此，如果你想使相应额外属性可用于DTO，你需要为相应的DTO显式定义相同的属性（如上所述）。

                                        如果你想允许在用户创建时设置它，你还需要为IdentityUserCreateDto定义它。


                                        如果属性不那么安全，这可能会很乏味。对象扩展系统允许你忽略此定义检查所需属性。请参阅下面的示例：

                                                        ObjectExtensionManager.Instance
                                                                                .AddOrUpdateProperty<IdentityUser, string>(
                                                                                    "SocialSecurityNumber",
                                                                                    options =>
                                                                                    {
                                                                                        options.MapEfCore(b => b.HasMaxLength(32));
                                                                                        options.CheckPairDefinitionOnMapping = false;
                                                                                    }
                                                                                );

                                        这是另一种定义实体属性的方法（ObjectExtensionManager有更多，请参阅其文档）。

                                        这一次，我们将CheckPairDefinitionOnMapping设置为false以跳过定义检查，同时将实体映射到DTO，反之亦然。

                                        如果你不喜欢这种方法，但想更轻松地将单个属性添加到多个对象（DTO），AddOrUpdateProperty可以获取一个类型数组来添加额外的属性：

                                                        ObjectExtensionManager.Instance
                                                                                .AddOrUpdateProperty<string>(
                                                                                    new[]
                                                                                    {
                                                                                        typeof(IdentityUserDto),
                                                                                        typeof(IdentityUserCreateDto),
                                                                                        typeof(IdentityUserUpdateDto)
                                                                                    },
                                                                                    "SocialSecurityNumber"
                                                                                );
                */
                /*
                    六、关于用户界面

                            该系统允许你向实体和DTO添加额外的属性并执行自定义业务代码，但它与用户界面无关。

                            请参阅覆盖UI部分的用户界面指南。  https://abp.io/docs/latest/framework/architecture/modularity/extending/overriding-user-interface
                */
        </script>
    </body>
</html>